# Copyright The IETF Trust 2011-2022, All Rights Reserved
# -*- coding: utf-8 -*-


import re

from unidecode import unidecode

from django import forms
from django.contrib.auth.models import User
from django.contrib.auth import password_validation
from django.core.exceptions import ValidationError
from django.db import models

from ietf.person.models import Person, Email
from ietf.mailinglists.models import Allowlisted
from ietf.utils.text import isascii
from .password_validation import StrongPasswordValidator

from .validators import prevent_at_symbol, prevent_system_name, prevent_anonymous_name, is_allowed_address
from .widgets import PasswordStrengthInput, PasswordConfirmationInput


class RegistrationForm(forms.Form):
    email = forms.EmailField(label="Your email (lowercase)")

    def clean_email(self):
        email = self.cleaned_data.get('email', '')
        if not email:
            return email
        if email.lower() != email:
            raise forms.ValidationError('The supplied address contained uppercase letters.  Please use a lowercase email address.')
        return email


class PasswordStrengthField(forms.CharField):
    widget = PasswordStrengthInput(
        attrs={
            "class": "password_strength",
            "data-disable-strength-enforcement": "",  # usually removed in init
        }
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for pwval in password_validation.get_default_password_validators():
            if isinstance(pwval, password_validation.MinimumLengthValidator):
                self.widget.attrs["minlength"] = pwval.min_length
            elif isinstance(pwval, StrongPasswordValidator):
                self.widget.attrs.pop(
                    "data-disable-strength-enforcement", None
                )

    

class PasswordForm(forms.Form):
    password = PasswordStrengthField()
    password_confirmation = forms.CharField(widget=PasswordConfirmationInput(
                                                        confirm_with='password',
                                                        attrs={'class':'password_confirmation'}),
                                            help_text="Enter the same password as above, for verification.",)

    def __init__(self, *args, user=None, **kwargs):
        # user is a kw-only argument to avoid interfering with the signature
        # when this class is mixed with ModelForm in PersonPasswordForm
        self.user = user
        super().__init__(*args, **kwargs)

    def clean_password_confirmation(self):
        # clean fields here rather than a clean() method so validation is
        # still enforced in PersonPasswordForm without having to override its
        # clean() method
        password = self.cleaned_data.get("password")
        password_confirmation = self.cleaned_data.get("password_confirmation")
        if password != password_confirmation:
            raise ValidationError(
                "The password confirmation is different than the new password" 
            )
        try:
            password_validation.validate_password(password_confirmation, self.user)
        except ValidationError as err:
            self.add_error("password", err)
        return password_confirmation


def ascii_cleaner(supposedly_ascii):
    outside_printable_ascii_pattern = r'[^\x20-\x7F]'
    if re.search(outside_printable_ascii_pattern, supposedly_ascii):
        raise forms.ValidationError("Only unaccented Latin characters are allowed.")
    return supposedly_ascii


class PersonPasswordForm(forms.ModelForm, PasswordForm):

    class Meta:
        model = Person
        fields = ['name', 'ascii']

    def clean_name(self):
        name = self.cleaned_data.get('name', '')
        prevent_at_symbol(name)
        prevent_system_name(name)
        prevent_anonymous_name(name)

        return name

    def clean_ascii(self):
        ascii = self.cleaned_data.get('ascii', '')
        if not isascii(ascii):
            raise forms.ValidationError("Ascii name contains non-ASCII characters.")

        return ascii

def get_person_form(*args, **kwargs):

    exclude_list = ['time', 'user', 'photo_thumb', 'photo', ]

    person = kwargs['instance']
    roles = person.role_set.all()
    if not roles:
        exclude_list += ['biography', 'photo', ]

    class PersonForm(forms.ModelForm):
        class Meta:
            model = Person
            exclude = exclude_list           

        def __init__(self, *args, **kwargs):
            super(PersonForm, self).__init__(*args, **kwargs)

            # blank ascii if it's the same as name
            self.fields["ascii"].required = self.fields["ascii"].widget.is_required = False
            self.fields["ascii"].help_text += " " + "Leave blank to use auto-reconstructed Latin version of name."

            if self.initial.get("ascii") == self.initial.get("name"):
                self.initial["ascii"] = ""

            self.fields['pronouns_selectable'] = forms.MultipleChoiceField(label='Pronouns', choices = [(option, option) for option in ["he/him", "she/her", "they/them"]], widget=forms.CheckboxSelectMultiple, required=False)
            self.fields["pronouns_freetext"].widget.attrs.update(
                {"aria-label": "Optionally provide your personal pronouns"}
            )

            self.unidecoded_ascii = False

            if self.data and not self.data.get("ascii", "").strip():
                self.data = self.data.copy()
                name = self.data["name"]
                reconstructed_name = unidecode(name)
                self.data["ascii"] = reconstructed_name
                self.unidecoded_ascii = name != reconstructed_name


        def clean_name(self):
            name = self.cleaned_data.get("name") or ""
            prevent_at_symbol(name)
            prevent_system_name(name)
            return name

        def clean_ascii(self):
            if self.unidecoded_ascii:
                raise forms.ValidationError("Name contained non-ASCII characters, and was automatically reconstructed using only Latin characters. Check the result - if you are happy, just hit Submit again.")

            name = self.cleaned_data.get("ascii") or ""
            prevent_at_symbol(name)
            prevent_system_name(name)
            return ascii_cleaner(name)

        def clean_ascii_short(self):
            name = self.cleaned_data.get("ascii_short") or ""
            prevent_at_symbol(name)
            prevent_system_name(name)
            return ascii_cleaner(name)

        def clean(self):
            if self.cleaned_data.get("pronouns_selectable") and self.cleaned_data.get("pronouns_freetext"):
                self.add_error("pronouns_freetext", "Either select from the pronoun checkboxes or provide a custom value, but not both")

    return PersonForm(*args, **kwargs)


class NewEmailForm(forms.Form):
    new_email = forms.EmailField(label="New email address", required=False, validators=[is_allowed_address])


class RoleEmailForm(forms.Form):
    email = forms.ModelChoiceField(label="Role email", queryset=Email.objects.all())

    def __init__(self, role, *args, **kwargs):
        super(RoleEmailForm, self).__init__(*args, **kwargs)

        f = self.fields["email"]
        f.label = "%s in %s" % (role.name, role.group.acronym.upper())
        f.help_text = "Email to use for <i>%s</i> role in %s" % (role.name, role.group.name)
        f.queryset = f.queryset.filter(models.Q(person=role.person_id) | models.Q(role=role)).distinct()
        f.initial = role.email_id
        f.choices = [(e.pk, e.address if e.active else "({})".format(e.address)) for e in f.queryset]


class ResetPasswordForm(forms.Form):
    username = forms.EmailField(label="Your email (lowercase)")


class TestEmailForm(forms.Form):
    email = forms.EmailField(required=False)

class AllowlistForm(forms.ModelForm):
    class Meta:
        model = Allowlisted
        exclude = ['by', 'time' ]


class ChangePasswordForm(PasswordForm):
    current_password = forms.CharField(widget=forms.PasswordInput)
    field_order = ["current_password", "password", "password_confirmation"]

    def __init__(self, user, *args, **kwargs):
        # user arg is optional in superclass, but required for this form
        super().__init__(*args, user=user, **kwargs)

    def clean_current_password(self):
        # n.b., password = None is handled by check_password and results in a failed check
        password = self.cleaned_data.get("current_password", None)
        if not self.user.check_password(password):
            raise ValidationError("Invalid password")
        return password


class ChangeUsernameForm(forms.Form):
    username = forms.ChoiceField(choices=[('-','--------')])
    password = forms.CharField(widget=forms.PasswordInput, help_text="Confirm the change with your password")

    def __init__(self, user, *args, **kwargs):
        assert isinstance(user, User)
        super(ChangeUsernameForm, self).__init__(*args, **kwargs)
        self.user = user
        emails = user.person.email_set.filter(active=True)
        choices = [ (email.address, email.address) for email in emails ]
        self.fields['username'] = forms.ChoiceField(choices=choices)

    def clean_password(self):
        password = self.cleaned_data['password']
        if not self.user.check_password(password):
            raise ValidationError('Invalid password')
        return password

    def clean_username(self):
        username = self.cleaned_data['username']
        if User.objects.filter(username__iexact=username).exists():
            raise ValidationError("A login with that username already exists.  Please contact the secretariat to get this resolved.")
        return username
